%option outfile="pylex.cc" header-file="pylex.h"
%option yylineno
%option stack

/**
 * Analizador Léxico de Python
 * Autores: José Daniel Quintos López
 *          Alfonso Edmundo Sánchez López
**/

%{
#include <string>
#include <stack>
#include <iostream>
#include <fstream>
#include <algorithm>

using namespace std;

ofstream output_file;
void redirect_streams(char *file_name);

stack<int> indent_stack;
int blanks_read;
void init_stack();
void determine_indent();

%}

%x INDENT
%s IMPLICITJOIN

keyword             and|del|for|assert|elif|from|break|else|global|class|except|if|continue|exec|import|def|finally|in|print|is|raise|lambda|return|not|try|or|while|pass|yield
operator            "+"|"-"|"*"|"**"|"/"|"//"|"%"|"<<"|">>"|"&"|"|"|"^"|"~"|"<"|">"|"<="|">="|"=="|"!="|"<>"
delimiter           "@"|","|"::"|":"|"."|"`"|"="|";"|"+="|"-="|"*="|"/="|"//="|"%="|"&="|"|="|"^="|">>="|"<<="|"**="

implicitinit        "["|"("|"{"
implicitend	        "]"|")"|"}"	

identifier          ([[:alpha:]]|_)([[:alnum:]]|_)*

integer             ([1-9][0-9]*|0)|(0[0-7]+)|(0[Xx][[:xdigit:]]+)
longinteger         {integer}[Ll]
pointfloat          ([0-9]*\.[0-9]+)|([0-9]+\.)
exponentfloat       ([0-9]+|{pointfloat})[eE][+\-]?[0-9]+
floatnumber     	{pointfloat}|{exponentfloat}
imagnumber          ({floatnumber}|[0-9]+)[jJ]
number              ({integer}|{longinteger}|{floatnumber}|{imagnumber})

escapeseq           \\.
stringprefix	    r|u|ur|R|U|UR|Ur|uR
shortstring         '([^'\n]|{escapeseq})*'|\"([^"\n]|{escapeseq})*\"
longstring          '''([^']|{escapeseq}|\'|\'\')*'''|\"\"\"([^"]|{escapeseq}|\"|\"\")*\"\"\"
stringliteral	    {stringprefix}?({shortstring}|{longstring})

%%
                    {                               
                        blanks_read = 0;                // En un principio inicializamos
                        indent_stack.push(0);           // el stack y pasamos a la start
                        BEGIN(INDENT);                  // condition indent
                    }                                   
                                                        
<INDENT>"\n"        blanks_read = 0;                    // Una lína vacía o de comentarios
<INDENT>#.*         blanks_read = 0;                    // es ignorada (reiniciamos contador)
                                                        
<INDENT>" "         ++blanks_read;                      // Los blancos incrementan el contador

<INDENT>"\t"        {
                        int tab_size = 8;               // Si vemos un tabulador
                        tab_size -= blanks_read % 8;    // incrementamos el contador
                        blanks_read += tab_size;        // al próximo múltiplo de 8
                    }


<INDENT>.           {   
                        unput(*yytext);                 // Cuando vemos el primer caracter "de verdad"
                        determine_indent();             // lo devolvemos al stream, calculamos los
                        BEGIN(INITIAL);                 // indents y volvemos a la start condition
                    }                                   // estándar
                    

<IMPLICITJOIN>"\n"              //ignoramos el salto de línea
<IMPLICITJOIN>{implicitend} {   //salimos del contexto actual
		                        cout << yytext << " ";
		                        yy_pop_state();
		                    }	
                    
[[:blank:]]         // ignoramos blancos
#.*                 // ignoramos comentarios
"\\\n"              // ignoramos NEWLINE después de diagonal (explicit join)
"\n"                {
                        cout << "NEWLINE" << endl;
                        BEGIN(INDENT);
                    }
                    
{keyword}           {   
                        int i;
                        for(i = 0; yytext[i]; i++)
                             yytext[i] = toupper(yytext[i]); 
                        cout << yytext << " ";
                    }
{operator}          cout << yytext << " ";
{delimiter}         cout << yytext << " ";

{implicitinit} 	    {
		                cout << yytext << " ";
			            yy_push_state(IMPLICITJOIN);
			            //BEGIN(IMPLICITJOIN);
                    }

{implicitend}       cout << yytext << " ";  

{stringliteral}     cout << "STRING ";
{number}            cout << "NUMBER ";
{identifier}        cout << "IDENTIFIER ";
.                   cerr << yylineno << " -> caracter inválido: " << yytext << endl;


%%
/**
 *  Esta función hace redirección de la entrada de Flex y de la salida
 *  estándar a los archivos "py" y "pyl" respectivamente
 *
 */
void redirect_streams(char *file_name)
{
    string in_filename(file_name);
    string out_filename(in_filename + 'l');
    
    // Designamos el archivo de entrada *.py como la entrada de lex (yyin)
    // esto lo hacemos al viejo estilo de C
    yyin = fopen(in_filename.c_str(), "r");
    if (!yyin) {
        cerr << "Error: no se pudo leer el archivo de entrada" << endl;
        exit(EXIT_FAILURE);
    }
    
    // Redireccionamos la salida estándar al archivo de salida *.pyl
    // esto lo hacemos con streams de C++
    output_file.open(out_filename.c_str());
    if (!output_file.is_open()) {
        cerr << "Error: no se pudo abrir el archivo de salida" << endl;
        exit(EXIT_FAILURE);
    } else {
        cout.rdbuf(output_file.rdbuf());
    }
}

/**
 * Esta función se llama cuando encontramos el primer caracter (no blanco) de
 * una línea lógica o si llegamos al fin del archivo. Sirve para determinar si
 * hay que hacer un INDENT o DEDENT dependiendo de los contenidos de 
 * blanks_read e indent_stack
 */
void determine_indent() 
{
    if (blanks_read > indent_stack.top()) {
        indent_stack.push(blanks_read);
        cout << "INDENT ";
    } else if (blanks_read < indent_stack.top()) {
        while (blanks_read < indent_stack.top()) {
            indent_stack.pop();
            cout << "DEDENT ";
        }
        
        if (blanks_read != indent_stack.top()) {
            cerr << yylineno << " -> Sangría inconsistente" << endl;
        }
    }
    blanks_read = 0;
}

/**
 * Al llegar al final del archivo tenemos que vaciar el stack de indentación
 */
int yywrap()
{
    blanks_read = 0;
    determine_indent();
    return 1;
}


int main(int argc, char* argv[]){    
    if (argc !=2) {
        cerr << "Error: número de argumentos inválido, debes especificar el nombre" << endl
             << "       del archivo de entrada, ejemplo:" << endl
             << "		pylex entrada.py" << endl;
        exit(EXIT_FAILURE);
    }    
    
    redirect_streams(argv[1]);
        
    yylex();
        
    exit(EXIT_SUCCESS);
}
